;(function(root, factory){
    if (typeof exports === "object") {
        // CommonJS
        module.exports = exports = factory();
    }
    else if (typeof define === "function" && define.amd) {
        // AMD
        define([], factory);
    }
    else {
        // Global (browser)
        root.BitUtil = factory();
    }
})(this, function(){
    let bitUtil = {};
    function checkLength(byteGetter, index, length){
        return byteGetter.valid(index + length - 1);
    }

    function makeChar(b1, b0){
        return String.fromCharCode((b1 << 8) | (b0 & 0xff));
    }
    function makeShort(b1, b0){
        return (b1 << 8) | (b0 & 0xff)
    }
    function get2ByteB(func, byteGetter, index){
        return func(byteGetter.get(index), byteGetter.get(index+1));
    }
    function get2ByteL(func, byteGetter, index){
        return func(byteGetter.get(index+1), byteGetter.get(index));
    }


    function makeInt(b3, b2, b1, b0){
        return (((b3 & 0xff) << 24) | ((b2 & 0xff) << 16) |
            ((b1 & 0xff) <<  8) | (b0 & 0xff));
    }
    function get4ByteB(func, byteGetter, index){
        return func(byteGetter.get(index), byteGetter.get(index+1), byteGetter.get(index+2), byteGetter.get(index+3));
    }
    function get4ByteL(func, byteGetter, index){
        return func(byteGetter.get(index+3), byteGetter.get(index+2), byteGetter.get(index+1), byteGetter.get(index))
    }

    function makeFloat(b3, b2, b1, b0){
        let buffer = new ArrayBuffer(4);
        let float = new Uint8Array(buffer);
        float[0] = b3;
        float[1] = b2;
        float[2] = b1;
        float[3] = b0;
        var dataView = new DataView(buffer, 0, 4);
        return dataView.getFloat32(0);
    }

    function makeLong(b7, b6, b5, b4, b3, b2, b1, b0){
        return  ((b7 & 0xff) << 56) | ((b6 & 0xff) << 48) |
            ((b5 & 0xff) << 40) | ((b4 & 0xff) << 32) |
            ((b3 & 0xff) << 24) | ((b2 & 0xff) << 16) |
            ((b1 & 0xff) <<  8) | ((b0 & 0xff));
    }
    function get8ByteB(func, byteGetter, index){
        return func(
            byteGetter.get(index  ), byteGetter.get(index+1),
            byteGetter.get(index+2), byteGetter.get(index+3),
            byteGetter.get(index+4), byteGetter.get(index+5),
            byteGetter.get(index+6), byteGetter.get(index+7));
    }
    function get8ByteL(func, byteGetter, index){
        return func(
            byteGetter.get(index+7), byteGetter.get(index+6),
            byteGetter.get(index+5), byteGetter.get(index+4),
            byteGetter.getChar(index+3), byteGetter.get(index+2),
            byteGetter.get(index+1), byteGetter.get(index  ));
    }

    function makeDouble(b7, b6, b5, b4, b3, b2, b1, b0){
        let buffer = new ArrayBuffer(8);
        let float = new Uint8Array(buffer);
        float[0] = b7;
        float[1] = b6;
        float[2] = b5;
        float[3] = b4;
        float[4] = b3;
        float[5] = b2;
        float[6] = b1;
        float[7] = b0;
        var dataView = new DataView(buffer, 0, 8);
        return dataView.getFloat64(0);
    }
    bitUtil.getChar = function(byteGetter, index, bigEndian){
        if (!checkLength(byteGetter, index, 2)){
            throw "ArrayIndexOutOfBoundsException";
        }
        return bigEndian?get2ByteB(makeChar, byteGetter, index):get2ByteL(makeChar, byteGetter, index);
    }
    bitUtil.getShort = function(byteGetter, index, bigEndian){
        if (!checkLength(byteGetter, index, 2)){
            throw "ArrayIndexOutOfBoundsException";
        }
        return bigEndian?get2ByteB(makeShort, byteGetter, index):get2ByteL(makeShort, byteGetter, index);
    }
    bitUtil.getInt = function(byteGetter, index, bigEndian){
        if (!checkLength(byteGetter, index, 4)){
            throw "ArrayIndexOutOfBoundsException";
        }
        return bigEndian?get4ByteB(makeInt, byteGetter, index):get4ByteL(makeInt, byteGetter, index);
    }
    bitUtil.getFloat = function(byteGetter, index, bigEndian){
        if (!checkLength(byteGetter, index, 4)){
            throw "ArrayIndexOutOfBoundsException";
        }
        return bigEndian?get4ByteB(makeFloat, byteGetter, index):get4ByteL(makeFloat, byteGetter, index);
    }
    bitUtil.getLong = function(byteGetter, index, bigEndian){
        if (!checkLength(byteGetter, index, 8)){
            throw "ArrayIndexOutOfBoundsException";
        }
        return bigEndian?get8ByteB(makeLong, byteGetter, index):get8ByteL(makeLong, byteGetter, index);
    }
    bitUtil.getDouble = function(byteGetter, index, bigEndian){
        if (!checkLength(byteGetter, index, 8)){
            throw "ArrayIndexOutOfBoundsException";
        }
        return bigEndian?get8ByteB(makeDouble, byteGetter, index):get8ByteL(makeDouble, byteGetter, index);
    }






    //将数值写入到视图中，获得其字节数组
    function getUint8Array(len, setNum) {
        var buffer = new ArrayBuffer(len);  //指定字节长度
        setNum(new DataView(buffer));  //根据不同的类型调用不同的函数来写入数值
        return new Uint8Array(buffer); //创建一个字节数组，从缓存中拿取数据
    }
    function getDoubleBytes(num, bigEndian) {
        return getUint8Array(8, function (view) { view.setFloat64(0, num, !bigEndian); })
    }
    function getLongBytes(num, bigEndian) {
        return getUint8Array(8, function (view) { view.setBigInt64(0, num, !bigEndian); })
    }
    function getFloatBytes(num, bigEndian) {
        return getUint8Array(4, function (view) { view.setFloat32(0, num, !bigEndian); })
    }
    function getIntBytes(num, bigEndian) {
        return getUint8Array(4, function (view) { view.setInt32(0, num, !bigEndian); })
    }
    function getShortBytes(num, bigEndian) {
        return getUint8Array(2, function (view) { view.setInt16(0, num, !bigEndian); })
    }
    function getChar(c, bigEndian) {
        let num = c.charCodeAt(1);
        return getUint8Array(2, function (view) { view.setUint16(0, num, !bigEndian); })
    }


    bitUtil.putChar = function(byteSetter, char, bigEndian){
        byteSetter.put(getChar(char, bigEndian));
    }
    bitUtil.putShort = function(byteSetter, short, bigEndian){
        byteSetter.put(getShortBytes(short, bigEndian));
    }
    bitUtil.putInt = function(byteSetter, int, bigEndian){
        byteSetter.put(getIntBytes(int, bigEndian));
    }
    bitUtil.putFloat = function(byteSetter, float, bigEndian){
        var s = getFloatBytes(float, bigEndian);
        byteSetter.put(s);
    }
    bitUtil.putLong = function(byteSetter, long, bigEndian){
        byteSetter.put(getLongBytes(long, bigEndian));
    }
    bitUtil.putDouble = function(byteSetter, double, bigEndian){
        byteSetter.put(getDoubleBytes(double, bigEndian));
    }

    bitUtil.byteArray2Hex = function(bytes) {
        if (!(Array.isArray(bytes) || bytes instanceof Uint8Array)) {
            throw "unsupported";
        }
        var str = "";
        for (var byte of bytes) {
            var hex = byte.toString(16);
            if (byte < 16) {
                hex = "0" + hex;
            }
            str += hex;
        }
        return str;
    }

    bitUtil.hex2ByteArray = function(str) {
        if (!("string" === typeof str)) {
            throw "unsupported";
        }
        var array = [];
        var index = 0;
        for (var i = 0, l = str.length; i < l; i += 2) {
            array[index++] =  parseInt("0x" + str[i] + str[i+1]);

        }
        return array;
    }

    return bitUtil;
});

(function (root, factory) {
        if (typeof exports === "object") {
            // CommonJS
            module.exports = exports = factory();
        }
        else if (typeof define === "function" && define.amd) {
            // AMD
            define([], factory);
        }
        else {
            // Global (browser)
            root.ByteBuffer = factory();
        }
    }(this, function() {
        return function (bigEndian) {
            var buffer = new Uint8Array(1024);
            var offset = 0;
            var length = 0;
            var expansionSize = 1024;
            var bigEndian = bigEndian===false?false:true;
            var byteBuffer = {};
            function expansion(len) {
                if (buffer.length < length + len) {
                    var expansion = length + len - buffer.length;
                    var size = Math.ceil(expansion/expansionSize);
                    var temp = new Uint8Array(buffer.length + size);
                    temp.set(buffer, 0);
                    buffer = temp;
                }
            }
            byteBuffer.valid = function(index) {
                return length > index;
            }
            byteBuffer.get = function(index) {
                if (index === undefined || index == null) {
                    throw "ErrorIndexBoundsException";
                }
                if (length <= index) {
                    throw "ArrayIndexOutOfBoundsException";
                }
                return buffer[index] & 0xff;
            }
            byteBuffer.put = function (uint8Array) {
                expansion(uint8Array.length);
                buffer.set(uint8Array, length);
                length += uint8Array.length;
            }
            byteBuffer.getByte = function() {
                var byte = byteBuffer.get(offset);
                offset ++;
                return byte;
            }
            byteBuffer.getBytes = function(length) {
                var bytes = [];
                for (var i = 0; i < length; i++) {
                    bytes.push(byteBuffer.getByte());
                }
                return bytes;
            }

            byteBuffer.putBytes = function(bytes) {
                var buffer = new ArrayBuffer(bytes.length);  //指定字节长度
                var view = new DataView(buffer);  //根据不同的类型调用不同的函数来写入数值
                for (var i = 0; i < bytes.length; i ++){
                    view.setUint8(i, bytes[i]&0xff);
                }
                byteBuffer.put(new Uint8Array(buffer)); //创建一个字节数组，从缓存中拿取数据
            }

            byteBuffer.putByte = function(byte) {
                byteBuffer.putBytes([byte]); //创建一个字节数组，从缓存中拿取数据
            }

            byteBuffer.getChar = function() {
                var c = BitUtil.getChar(byteBuffer, offset, bigEndian);
                offset += 2;
                return c;
            }
            byteBuffer.putChar = function(char) {
                BitUtil.putChar(byteBuffer, char, bigEndian);
            }

            byteBuffer.getString = function(len) {
                var rst = "";
                for (var i = 0; i < len; i++) {
                    rst = rst + String.fromCharCode(byteBuffer.getByte());
                }
                return rst;
            }

            byteBuffer.putString = function(chars) {
                for (var i = 0, l = chars.length; i < l; i++) {
                    byteBuffer.putByte(chars[i].charCodeAt());
                }
            }

            byteBuffer.getShort = function() {
                var s = BitUtil.getShort(byteBuffer, offset, bigEndian);
                offset += 2;
                return s;
            }
            byteBuffer.putShort = function(short) {
                BitUtil.putShort(byteBuffer, short, bigEndian);
            }

            byteBuffer.getInt = function() {
                var s = BitUtil.getInt(byteBuffer, offset, bigEndian);
                offset += 4;
                return s;
            }
            byteBuffer.putInt = function(int) {
                BitUtil.putInt(byteBuffer, int, bigEndian);
            }

            byteBuffer.getFloat = function() {
                var s = BitUtil.getFloat(byteBuffer, offset, bigEndian);
                offset += 4;
                return s;
            }
            byteBuffer.putFloat = function(float) {
                BitUtil.putFloat(byteBuffer, float, bigEndian);
            }
            // js不支持64位的long
            /*
            byteBuffer.getLong = function() {
                var s = BitUtil.getLong(byteBuffer, offset, bigEndian);
                offset += 8;
                return s;
            }
            byteBuffer.putLong = function(long) {
                BitUtil.putLong(byteBuffer, long, bigEndian);
            }
            */

            byteBuffer.getDouble = function() {
                var s = BitUtil.getDouble(byteBuffer, offset, bigEndian);
                offset += 8;
                return s;
            }
            byteBuffer.putDouble = function (double) {
                BitUtil.putDouble(byteBuffer, double, bigEndian);
            }
            byteBuffer.remaining = function() {
                return length - offset;
            }
            byteBuffer.asUint8Array = function () {
                return buffer.slice(0, length);
            }
            byteBuffer.clear = function () {
                offset = 0;
                length = 0;
            }
            return byteBuffer;
        }
    })
);




























;(function(root, factory){
    if (typeof exports === "object") {
        // CommonJS
        module.exports = exports = factory();
    }
    else if (typeof define === "function" && define.amd) {
        // AMD
        define([], factory);
    }
    else {
        // Global (browser)
        root.WEBSOCKET = factory();
    }
})(this, function(){
    const Message = {
        // 魔法数
        MAGIC: 0xb4,
        // 协议版本
        VERSION: 0x00,
        // 进度信息
        TYPE_CONTROL_PROGRESS: 0x01,
        // 源信息
        TYPE_CONTROL_SRC: 0x02,
        // 播放状态信息
        TYPE_CONTROL_ISPLAY: 0x04,
        // 倍速信息
        TYPE_CONTROL_SPEED: 0x08,
        //
        TYPE_CONTROL_GETTER: 0x10,
        // 系统消息
        TYPE_MESSAGE_SYSTEM: 0x80,
        // 用户信息
        TYPE_MESSAGE_USER: 0x81,
        // 校验码长度
        CHECKSUM_LEN: 4,
        // 掩码长度
        MASKKEY_LEN: 4,
    }
    function checksum(data) {
        var cs = [];// new byte[CHECKSUM_LEN];
        for (var i = 0, l = Message.CHECKSUM_LEN; i < l; i++) {
            cs[i] = 0;
        }
        for (var i = 0, l = data.length; i < l; i++) {
            cs[i%Message.CHECKSUM_LEN] = (cs[i%Message.CHECKSUM_LEN] + data[i]) & 0xff;
        }
        return cs;
    }

    function check(data, checksumData) {
        const checkData = checksum(data);
        for (var i = 0, l = Message.CHECKSUM_LEN; i < l; i++) {
            if ( checkData[i] != checksumData[i]) {
                return false
            }
        }
        return true;
    }

    function badFrameException() {
        throw "bad frame data";
    }

    function afterMask(mask, data) {
        if (mask.length != Message.MASKKEY_LEN) {
            badFrameException();
        }
        var temp = new Array(data.length);
        for (var i = 0, l = data.length; i < l; i++) {
            temp[i] = (data[i] ^ mask[i%Message.MASKKEY_LEN]) & 0xff;
        }
        return temp;
    }
    function Package() {
        var option = 0;
        var data = [];
        return {
            setOperation: (op) => {
                option = op;
                return this;
            },
            setData: (d) => {
                data = d;
                return this;
            },
            asByteBuffer: () => {
                var buffer = new ByteBuffer();
                buffer.putByte(Message.MAGIC);
                buffer.putByte(Message.VERSION);
                var mask = new Array(Message.MASKKEY_LEN);
                for (var i = 0; i < Message.MASKKEY_LEN; i ++) {
                    mask[i] = Math.floor(Math.random()*256);
                }
                buffer.putByte(option&0xff);
                buffer.putShort(data.length);
                buffer.putBytes(mask);
                const maskData = afterMask(mask, data);
                const checksumData = checksum(maskData);
                buffer.putBytes(checksumData);
                buffer.put(maskData);
                return buffer;
            }
        }
    }

    function MessageHandler(dataHandler) {
        function parse(byteBuffer) {
            if (byteBuffer.getByte() !== Message.MAGIC || byteBuffer.getByte() !== Message.VERSION) {
                return ;
            }
            const type = byteBuffer.getByte();

            const len = byteBuffer.getShort();
            const mask = byteBuffer.getBytes(Message.MASKKEY_LEN);
            const checksum = byteBuffer.getBytes(Message.CHECKSUM_LEN);
            const maskData = byteBuffer.getBytes(len);
            if (!check(maskData, checksum)) {
                badFrameException();
            }
            const data = afterMask(mask, maskData);
            const bf = new ByteBuffer();
            bf.put(data);
            return {
                type: type,
                data: bf
            };
        }
        return {
            handle: function (webSocket, byteBuffer){
                const data = parse(byteBuffer);
                const rst = dataHandler.handle(data, webSocket, Message);
                if (rst === null || rst === undefined) {
                    return null;
                }
                var package = new Package();
                package.setOperation(rst.type);
                package.setData(rst.data.asUint8Array());
                return package;
            },
            create: function (data) {
                const rst = dataHandler.create(data, Message);
                var package = new Package();
                package.setOperation(rst.type);
                package.setData(rst.data.asUint8Array());
                return package;
            }
        }
    }

    function BinWebSocket(url, handler) {
        var dom = document.createElement("div");
        var ws = new WebSocket(url);
        ws.binaryType = "arraybuffer";
        var webSocket = {
            url: url,
            binaryType: "arraybuffer",
        };

        Object.defineProperty(webSocket, 'binaryType', {
            writable: false,
            value: 'arraybuffer'
        });
        Object.defineProperty(webSocket, 'url', {
            writable: false,
            value: url
        });
        Object.defineProperty(webSocket, 'bufferedAmount', {
            writable: false,
            value: ws.bufferedAmount
        })
        Object.defineProperty(webSocket, 'extensions', {
            writable: false,
            value: ws.extensions
        })
        Object.defineProperty(webSocket, 'protocol', {
            writable: false,
            value: ws.protocol
        })

        Object.defineProperty(webSocket, 'close', {
            writable: false,
            value: function(code, reason) {
                ws.close(code, reason);
            }
        })

        Object.defineProperty(webSocket, 'readyState', {
            get: () => ws.readyState
        })

        ws.onerror = function (evt) {
            dom.dispatchEvent(
                new CustomEvent("error", {
                    detail: { target: webSocket, name: "error", rootEvent: evt },
                    cancelable: true
                })
            );
        }

        ws.onopen = function(evt) {
            dom.dispatchEvent(
                new CustomEvent("open", {
                    detail: { target: webSocket, name: "open", rootEvent: evt },
                    cancelable: true
                })
            );
        };

        ws.onclose = function(evt) {
            dom.dispatchEvent(
                new CustomEvent("close", {
                    detail: { target: webSocket, name: "close", rootEvent: evt },
                    cancelable: true
                })
            );
        };

        ws.onmessage = function(evt) {
            if (evt.data instanceof ArrayBuffer) {
                var data = new Uint8Array(evt.data);
                var bf = new ByteBuffer();
                bf.put(data);
                const rst = handler.handle(webSocket, bf);
                if (rst !== null && rst !== undefined) {
                    webSocket.send(rst);
                }
            }
        };
        webSocket.send = function(data) {
            if ( !data.asUint8Array ) {
                throw "the types do not match";
            }
            ws.send(data.asUint8Array());
        };
        webSocket.sendPackage = function(data) {
            this.send(handler.create(data).asByteBuffer());
        };
        webSocket.addEventListener = function(type, listener, useCapture) {
            dom.addEventListener(type, listener, useCapture);
        }
        return webSocket;
    }

    return {
        MessageHandler: MessageHandler,
        BinWebSocket: BinWebSocket,
        Package: Package,
        Message: Message
    }
});















function shareRoomConnect(url, videoController) {
    var h = {
        handle: function(data, webSocket, Message) {
            const buffer = data.data;
            if ( (data.type & Message.TYPE_CONTROL_ISPLAY) != 0) {
                const play = buffer.getByte();
                if (play != videoController.status) {
                    if(play == 0x00) {
                        videoController.pause();
                    }
                    if(play == 0x01) {
                        videoController.play();
                    }
                }
            }
            if ( (data.type & Message.TYPE_CONTROL_SPEED) != 0 ) {
                const speed = buffer.getFloat();
                if (speed != videoController.playbackRate) {
                    videoController.playbackRate = speed;
                }
            }
            if ( (data.type & Message.TYPE_CONTROL_PROGRESS) != 0 ) {
                const progress = buffer.getDouble();
                if ( Math.abs(videoController.currentTime - progress) > 1) {
                    videoController.currentTime = progress;
                }
            }
            if ( (data.type & Message.TYPE_CONTROL_SRC) != 0 ) {
                const remaining = buffer.remaining();
                const source = buffer.getString(remaining);
                if (source != videoController.source) {
                    videoController.source = source;
                }
            }
            if ( (data.type & Message.TYPE_MESSAGE_SYSTEM) != 0 ) {

            }
            if ( (data.type & Message.TYPE_MESSAGE_USER) != 0 ) {

            }
            /*return {
                type: type,
                data: buffer
            }*/
            return null;
        },
        create: function(videoController, Message) {
            var type = 0;
            var buffer = new ByteBuffer();
            const data = videoController.diffData;
            if (data.status != -1) {
                buffer.putByte(data.status);
                type = type | Message.TYPE_CONTROL_ISPLAY;
            }
            if (data.playbackRate != -1) {
                buffer.putFloat(data.playbackRate);
                type = type | Message.TYPE_CONTROL_SPEED;
            }
            if (data.currentTime != -1) {
                buffer.putDouble(data.currentTime);
                type = type | Message.TYPE_CONTROL_PROGRESS;
            }
            if (data.source != null) {
                buffer.putString(data.source);
                type = type | Message.TYPE_CONTROL_SRC;
            }
            if (videoController.loadVideo) {
                type = type | Message.TYPE_CONTROL_GETTER;
            }
            if (videoController.uploadVideo) {
                type = 0 | type;
            }
            return {
                type: type,
                data: buffer
            }
        }
    }
    var handler = new WEBSOCKET.MessageHandler(h);
    var socket = new WEBSOCKET.BinWebSocket(url, handler);
    return socket;
}

function VideoController(videoElem) {
    if (videoElem === undefined) {
        videoElem = {};
    }
    var video = videoElem;
    var preData = {
        status: -1,
        currentTime: -1,
        playbackRate: -1,
        source: null
    };
    var controller = {
        play: () => {
            if (video.paused) {
                video.play();
            }
        },
        pause: () => {
            if (!video.paused) {
                video.pause();
            }
        }
    }
    var videoParam = {
        loadVideo: false,
        uploadVideo: false
    }
    Object.defineProperty(controller, "loadVideo", {
        get: () => {
            // 一次调用之后恢复
            const rst = videoParam.loadVideo;
            videoParam.loadVideo = false;
            return rst;
        },
        set: (bool) => {
            videoParam.loadVideo =  (bool === true ?true:false);
        }
    })
    Object.defineProperty(controller, "uploadVideo", {
        get: () => {
            // 一次调用之后恢复
            const rst = videoParam.uploadVideo;
            videoParam.uploadVideo = false;
            return rst;
        },
        set: (bool) => {
            videoParam.uploadVideo = (bool === true ?true:false);
        }
    })
    Object.defineProperty(controller, "status", {
        get: () => {
            return video.paused?0:1;
        }
    })
    Object.defineProperty(controller, "currentTime", {
        get: () => {
            return video.currentTime;
        },
        set: (currentTime) => {
            video.currentTime = currentTime;
        }
    })
    Object.defineProperty(controller, "playbackRate", {
        get: () => {
            return video.playbackRate;
        },
        set: (playbackRate) => {
            video.playbackRate = playbackRate;
        }
    })
    Object.defineProperty(controller, "source", {
        get: () => {
            const s = video.source();
            return s.type + "|" + s.src;
        },
        set: (source) => {
            const index = source.indexOf("|");
            if (index != -1) {
                const type = source.substring(0, index);
                const src = source.substring(index+1);
                video.source({type: type, src: src});
                return;
            }
            throw "source format error";
        }
    })
    Object.defineProperty(controller, "diffData", {
        get: () => {
            var data = {
                status: -1,
                currentTime: -1,
                playbackRate: -1,
                source: null
            };
            console.log("controller " + controller.status);
            console.log("pre " + preData.status);
            if (controller.status !== preData.status) {
                data.status = controller.status;
                preData.status = controller.status;
            }
            if (controller.currentTime !== preData.currentTime) {
                data.currentTime = controller.currentTime;
                preData.currentTime = controller.currentTime;
            }
            if (controller.playbackRate !== preData.playbackRate) {
                data.playbackRate = controller.playbackRate;
                preData.playbackRate = controller.playbackRate;
            }
            if (controller.source !== preData.source) {
                data.source = controller.source;
                preData.source = controller.source;
            }
            return data;
        }
    })
    controller.changeVideoElem = function(videoDom) {
        video = videoDom;
    }
    controller.ready = function() {
        return video.paused !== undefined;
    }
    return controller;
}